Make the shared template registry actually shared (#104) - #449
Conversation
#448 routed the validator, compiler and runtime through one filter registry and asserted it by comparing filter *names*. The compiler then called its own `_register_custom_filters()` on the very next line, replacing eleven of those implementations. The name sets matched, so the drift test passed, while the semantics differed: {{ 0 | default('X') }} runtime '0' compiler 'X' {{ '' | default('X') }} runtime '' compiler 'X' {{ false | default('X') }} runtime 'False' compiler 'X' {{ missing | default('X') }} runtime 'X' compiler UndefinedError The compiler's `default` treated every falsy value as absent, and raised on an undefined one -- the single case the filter exists for. `to_json`, `slugify` and `regex_search` diverged too. All eleven filters the compiler registered were already in the runtime registry and none were unique to it, so the 110-line method and its call site are deleted rather than reconciled. The environments now genuinely share one set. The drift test is replaced by behavioural comparison. `test_no_filter_diverges _between_environments` renders *every* filter in the registry through all three environments against probes chosen for where implementations part company -- undefined, None, empty string, zero, false, non-ASCII, a non-string -- and compares results, not registries. A hand-written table covers `default`'s undefined-versus-falsy distinction, malformed JSON, regex, paths and unicode explicitly, and three cases run end to end through the CLI so the assertion reaches a real file. Also fixes a test of mine that could not fail: it accepted any error whose text lacked the word "filter", and fed `from_json` the string "Hello World Report", which is not JSON -- so an unrelated rendering error satisfied it. It now asserts the pipeline compiles, on valid JSON. Mutation-tested: re-introducing the divergent `default` fails 10 tests including the CLI cases; adding a divergent `upper` -- a filter no hand-written case names -- fails the generic sweep. Blocking suite: 573 -> 602 passed, 0 failed. Catalogue unchanged at 18/117. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI verification9/9 green. Legacy tally against the current post-merge
The +29 deselected is exactly the 29 tests this PR adds to the blocking layer The -3 is not this PR fixing anythingA -3/+3 on a PR that changes filter semantics in the compiler is exactly the All three are in The number worth reading is newly failing: none. Deleting 110 lines of Warning classes are identical on both (2 RuntimeWarning, 1 Standing caveatAs on #448: the legacy layer's coverage of the template path is thin enough |
Addresses findings 1 and 2 of the review of #448.
The shared registry was not shared
#448 routed the validator, compiler and runtime through one filter registry and
asserted it by comparing filter names. The compiler called its own
_register_custom_filters()on the very next line, replacing eleven of thoseimplementations. Name sets matched, so the drift test passed. Semantics did not:
{{ 0 | default('X') }}'0''X'{{ '' | default('X') }}'''X'{{ false | default('X') }}'False''X'{{ missing | default('X') }}'X'The compiler's
defaulttreated every falsy value as absent and raised on anundefined one — the single case the filter exists for.
to_json,slugifyandregex_searchdiverged too, which the review had not caught.All eleven filters the compiler registered were already in the runtime registry
and none were unique to it, so the 110-line method and its call site are
deleted rather than reconciled.
Behaviour, not registries
test_no_filter_diverges_between_environmentsrenders every filter in theregistry through all three environments against probes chosen for where
implementations part company — undefined, None, empty string, zero, false,
non-ASCII, non-string — and compares results.
A hand-written table covers
default's undefined-vs-falsy distinction,malformed JSON, regex, paths and unicode explicitly. Three cases run end to end
through the CLI, so the assertion reaches a real file rather than stopping at an
environment.
Two comparison artefacts I had to handle rather than ignore: lazy filters render
as
<generator object ... at 0x...>whose address differs between any tworenders, and
random/shuffleare not functions of their input. Addresses aremasked; the two nondeterministic filters are excluded by name.
A test of mine that could not fail
test_a_pipeline_using_a_runtime_filter_validatesaccepted any error whose textlacked the word "filter", and fed
from_jsonthe literal"Hello World Report"— not JSON. An unrelated rendering error satisfied the assertion. It now asserts
the pipeline compiles, on valid JSON. That is the second test in two PRs where I
asserted on error wording instead of behaviour.
Evidence
defaultupper(in no hand-written case)ruff(CI rule set) andcompileallcleanNot in this PR
Deliberately, so each stays reviewable:
execution,pipeline,context,envare still accepted unconditionally and at least
executionis never populated.Fixing it makes more examples fail, and it wants a typed context schema that
validation and runtime population both derive from.
reaching consumers, dependency affecting scheduling, declared schema still
rejecting misspellings, nested fields).